package com.neusoft.bizcore.web.filter;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.springframework.util.StringUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * sql & xss。
 */
@Slf4j
public class XssSqlRequestWrapper extends HttpServletRequestWrapper {
    private static String key =
            "and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+";
    private static Set<String> notAllowedKeyWords = new HashSet<>(0);
    private static String replacedString = "INVALID";
    static {
        final String keyStr[] = XssSqlRequestWrapper.key.split("\\|");
        for (final String str : keyStr) {
            XssSqlRequestWrapper.notAllowedKeyWords.add(str);
        }
    }

    private final String currentUrl;
    private final String[] excludeUrls;

    public XssSqlRequestWrapper(HttpServletRequest servletRequest, String excludeUrls) {
        super(servletRequest);
        this.currentUrl = servletRequest.getRequestURI();
        if (XssSqlRequestWrapper.log.isDebugEnabled()) {
            XssSqlRequestWrapper.log.debug("xss sql filter is act on current url: {}", this.currentUrl);
        }
        this.excludeUrls = excludeUrls.split(",");
    }

    @Override
    public String getParameter(String parameter) {
        final String value = super.getParameter(parameter);
        if (value == null) {
            return null;
        }
        return this.cleanXSS(value);
    }

    @Override
    public String[] getParameterValues(String parameter) {
        final String[] values = super.getParameterValues(parameter);
        if (values == null) {
            return null;
        }
        final int count = values.length;
        final String[] encodedValues = new String[count];
        for (int i = 0; i < count; i++) {
            encodedValues[i] = this.cleanXSS(values[i]);
        }
        return encodedValues;
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        final Map<String, String[]> values = super.getParameterMap();
        if (values == null) {
            return null;
        }
        final Map<String, String[]> result = new HashMap<>();
        for (final String key : values.keySet()) {
            final String encodedKey = this.cleanXSS(key);
            final int count = values.get(key).length;
            final String[] encodedValues = new String[count];
            for (int i = 0; i < count; i++) {
                encodedValues[i] = this.cleanXSS(values.get(key)[i]);
            }
            result.put(encodedKey, encodedValues);
        }
        return result;
    }

    private String cleanXSS(String valueP) {
        if (this.checkWhiteList(this.excludeUrls, this.currentUrl)) {
            if (XssSqlRequestWrapper.log.isDebugEnabled()) {
                XssSqlRequestWrapper.log.debug("xss sql filter not include current url: {}", this.currentUrl);
            }
            return valueP;
        }
        // You'll need to remove the spaces from the html entities below
        String value = valueP.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
        value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
        value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
        value = value.replaceAll("'", "& #39;");
        value = value.replaceAll("eval\\((.*)\\)", "");
        value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
        value = value.replaceAll("script", "");
        value = this.cleanSqlKeyWords(value);
        return value;
    }

    private String cleanSqlKeyWords(String value) {
        String paramValue = value;
        for (final String keyword : XssSqlRequestWrapper.notAllowedKeyWords) {
            if ((paramValue.length() > (keyword.length() + 4))
                    && (paramValue.contains(" " + keyword) || paramValue.contains(keyword + " ")
                            || paramValue.contains(" " + keyword + " "))) {
                paramValue = StringUtils.replace(paramValue, keyword, XssSqlRequestWrapper.replacedString);
                XssSqlRequestWrapper.log
                        .warn(this.currentUrl + " had been filtered because it contains sql keywords(" + keyword
                                + ")" + "; original value is：" + value + ";filtered value is：" + paramValue);
            }
        }
        return paramValue;
    }

    /**
     * 将通配符表达式转化为正则表达式
     *
     * @param path
     * @return
     */
    private String getRegPath(String path) {
        final char[] chars = path.toCharArray();
        final int len = chars.length;
        final StringBuilder sb = new StringBuilder();
        boolean preX = false;
        for (int i = 0; i < len; i++) {
            if (chars[i] == '*') {//遇到*字符
                if (preX) {//如果是第二次遇到*，则将**替换成.*
                    sb.append(".*");
                    preX = false;
                } else if ((i + 1) == len) {//如果是遇到单星，且单星是最后一个字符，则直接将*转成[^/]*
                    sb.append("[^/]*");
                } else {//否则单星后面还有字符，则不做任何动作，下一把再做动作
                    preX = true;
                    continue;
                }
            } else {//遇到非*字符
                if (preX) {//如果上一把是*，则先把上一把的*对应的[^/]*添进来
                    sb.append("[^/]*");
                    preX = false;
                }
                if (chars[i] == '?') {//接着判断当前字符是不是?，是的话替换成.
                    sb.append('.');
                } else {//不是?的话，则就是普通字符，直接添进来
                    sb.append(chars[i]);
                }
            }
        }
        return sb.toString();
    }

    /**
     * 通配符模式
     *
     * @param excludePath - 不过滤地址
     * @param reqUrl - 请求地址
     * @return
     */
    private boolean filterUrls(String excludePath, String reqUrl) {
        final String regPath = this.getRegPath(excludePath);
        return Pattern.compile(regPath).matcher(reqUrl).matches();
    }

    /**
     * 检验是否在非过滤地址
     *
     * @param excludeUrls
     * @param reqUrl
     * @return
     */
    private boolean checkWhiteList(String[] excludeUrls, String reqUrl) {
        for (final String url : excludeUrls) {
            if (this.filterUrls(url, reqUrl)) {
                return true;
            }
        }
        return false;
    }

}
